package com.atguigu.gmall.search.service.impl;

import com.atguigu.gmall.search.entity.SearchAttr;
import com.atguigu.gmall.search.vo.*;
import com.google.common.collect.Lists;

import com.atguigu.gmall.search.entity.Goods;
import com.atguigu.gmall.search.respository.GoodsRepository;
import com.atguigu.gmall.search.service.SearchBizService;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.HighlightQuery;
import org.springframework.data.elasticsearch.core.query.HighlightQueryBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
public class SearchBizServiceImpl implements SearchBizService {

    @Autowired
    GoodsRepository goodsRepository; //对es的简单的crud

    @Autowired
    ElasticsearchRestTemplate esTemplate;


    @Override
    public void onSaleGoods(Goods goods) {
        goodsRepository.save(goods);
    }

    @Override
    public void cancelSale(Long skuId) {
        goodsRepository.deleteById(skuId);
    }

    //按照 paramVo 所有条件去es进行检索，检索完成后返回 SearchResponseVo
    @Override
    public SearchResponseVo search(SearchParamVo paramVo) {

        //1、根据前端传递来的检索条件，构造一个Query对象（代表要去es中搜索用的所有条件）。
        Query query = buildQuery(paramVo);

        //2、检索； 得到SearchHits 命中的记录结果
        SearchHits<Goods> search = esTemplate.search(query, Goods.class, IndexCoordinates.of("goods"));


        //3、根据es检索得到的结果，封装为前端能用的vo对象。
        SearchResponseVo responseVo = buildResponse(paramVo, search);

        return responseVo;
    }

    @Override
    public void updateHotScore(Long skuId, Long hotScore) {
        Optional<Goods> byId = goodsRepository.findById(skuId);

        Goods goods = byId.get();
        goods.setHotScore(hotScore);

        goodsRepository.save(goods);
    }

    /**
     * 构建检索条件
     * DSL ---- QueryBuilder构建出DSL
     * SQL ---- Mapper构建出SQL
     *
     * @param paramVo
     * @return
     */
    private Query buildQuery(SearchParamVo paramVo) {
        //根据 前端参数 构造出检索要用的 DSL 代表的 Query对象
        //1、bool
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        //1.1 must
        List<QueryBuilder> must = boolQuery.must();

        //根据不同条件构造must的子查询项
        //1.2 按照分类查询
        if (paramVo.getCategory1Id() != null) {
            must.add(QueryBuilders.termQuery("category1Id", paramVo.getCategory1Id()));
        }
        if (paramVo.getCategory2Id() != null) {
            must.add(QueryBuilders.termQuery("category2Id", paramVo.getCategory2Id()));
        }
        if (paramVo.getCategory3Id() != null) {
            must.add(QueryBuilders.termQuery("category3Id", paramVo.getCategory3Id()));
        }

        //1.3 按照关键字模糊检索
        if (!StringUtils.isEmpty(paramVo.getKeyword())) {
            must.add(QueryBuilders.matchQuery("title", paramVo.getKeyword()));
        }

        //1.4 按照品牌检索  3:华为
        if (!StringUtils.isEmpty(paramVo.getTrademark())) {
            long tmId = Long.parseLong(paramVo.getTrademark().split(":")[0]);
            must.add(QueryBuilders.termQuery("tmId", tmId));
        }

        //1.5 按照平台属性检索 props=4:256GB:机身存储 & props=1:4500-11999:价格
        if (paramVo.getProps() != null && paramVo.getProps().length > 0) {
            //遍历每一个属性检索条件  4:256GB:机身存储
            for (String prop : paramVo.getProps()) {
                String[] attrs = prop.split(":");
                BoolQueryBuilder query = QueryBuilders.boolQuery();
                //attrId必须是前端传来的
                query.must(QueryBuilders.termQuery("attrs.attrId", attrs[0]));
                //attrValue必须是前端传来的
                query.must(QueryBuilders.termQuery("attrs.attrValue", attrs[1]));
                //每个平台属性检索条件都要构造出一个嵌入式的查询
                must.add(QueryBuilders.nestedQuery("attrs", query, ScoreMode.None));
            }
        }

        //==================检索条件完成==========================

        //创建一个原生的DSL query对象
        NativeSearchQuery dsl = new NativeSearchQuery(boolQuery);

        //2、分页信息   pageNo=1
        PageRequest request = PageRequest.of(paramVo.getPageNo() - 1, paramVo.getPageSize());
        dsl.setPageable(request);

        //==================分页条件完成==========================

        //3、排序信息  order=2:asc
        if (!StringUtils.isEmpty(paramVo.getOrder())) {
            String[] split = paramVo.getOrder().split(":");
            Sort sort = null;
            switch (split[0]) {
                case "1":
                    sort = Sort.by("hotScore");
                    sort = split[1].equals("asc") ? sort.ascending() : sort.descending();
                    break;//热度排序
                case "2":
                    sort = Sort.by("price");
                    sort = split[1].equals("asc") ? sort.ascending() : sort.descending();
                    break;//价格排序
            }
            dsl.addSort(sort);
        }
        //===================排序条件完成 =====================


        //拼装聚合分析条件
        //4、分析 品牌id 值分布
        TermsAggregationBuilder tmIdAgg = AggregationBuilders
                .terms("tmIdAgg")
                .field("tmId")
                .size(200);
        //分析 同一个品牌id，他们的名和Logo
        //子聚合： 品牌名subAgg：
        TermsAggregationBuilder tmNameAgg = AggregationBuilders.terms("tmNameAgg")
                .field("tmName")
                .size(1);
        tmIdAgg.subAggregation(tmNameAgg);

        //子聚合： 品牌logo的subAgg
        TermsAggregationBuilder tmLogoAgg = AggregationBuilders.terms("tmLogoAgg")
                .field("tmLogoUrl")
                .size(1);
        tmIdAgg.subAggregation(tmLogoAgg);

        //聚合： 品牌id完成
        dsl.addAggregation(tmIdAgg);
        //=================聚合：【品牌】完成=================

        //5、分析 属性 值分布
        NestedAggregationBuilder attrAggNested = AggregationBuilders.nested("attrAgg", "attrs");

        // 属性id 聚合
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attrIdAgg")
                .field("attrs.attrId")
                .size(200);

        // 属性名 聚合
        TermsAggregationBuilder attrNameAgg = AggregationBuilders.terms("attrNameAgg")
                .field("attrs.attrName")
                .size(1);
        attrIdAgg.subAggregation(attrNameAgg);
        // 属性值 聚合
        TermsAggregationBuilder attrValueAgge = AggregationBuilders.terms("attrValueAgg")
                .field("attrs.attrValue")
                .size(200);
        attrIdAgg.subAggregation(attrValueAgge);

        attrAggNested.subAggregation(attrIdAgg);

        dsl.addAggregation(attrAggNested);
        //===============聚合：【属性】完成=================


        //6、设置高亮条件
        if (!StringUtils.isEmpty(paramVo.getKeyword())) {
            //模糊匹配到的字段就要高亮
            HighlightBuilder builder = new HighlightBuilder();
            builder.field("title")
                    .preTags("<span style='color:red'>")
                    .postTags("</span>");

            HighlightQuery query = new HighlightQuery(builder);
            dsl.setHighlightQuery(query);
        }


        return dsl;
    }


    /**
     * 构建响应结果
     *
     * @param paramVo 当时用的查询参数
     * @param search  检索到的数据
     * @return
     */
    private SearchResponseVo buildResponse(SearchParamVo paramVo, SearchHits<Goods> search) {
        SearchResponseVo response = new SearchResponseVo();

        //1、当时封装的所有检索条件原封不动返回
        response.setSearchParam(paramVo);

        //2、如果选了品牌就构建出 品牌面包屑
        if (!StringUtils.isEmpty(paramVo.getTrademark())) {
            String trademark = paramVo.getTrademark(); //trademark = 2:苹果
            response.setTrademarkParam("品牌:" + trademark.split(":")[1]);
        }

        //3、属性面包屑。 前端每带一个属性查询，把 属性名:属性值 作为一个面包屑
        if (paramVo.getProps() != null && paramVo.getProps().length > 0) {
            //["23:8G:运行内存","24:128G:机身内存"]
            List<SearchAttr> searchAttrs = Arrays.stream(paramVo.getProps())
                    .map(s -> {
                        SearchAttr searchAttr = new SearchAttr();
                        //"23:8G:运行内存"
                        String[] split = s.split(":");
                        if(split.length == 3){
                            searchAttr.setAttrId(Long.parseLong(split[0]));
                            searchAttr.setAttrValue(split[1]);
                            searchAttr.setAttrName(split[2]);
                        }
                        return searchAttr;
                    }).collect(Collectors.toList());

            //属性面包屑完成
            response.setPropsParamList(searchAttrs);
        }


        //4、品牌列表（分析检索到的所有商品涉及了多少种品牌，并封装每个品牌的详细信息）
        //把所有检索到的商品的品牌都拿出来，进行去重
        ParsedLongTerms tmIdAgg = search.getAggregations().get("tmIdAgg");
        List<SearchTmVo> tmVos = tmIdAgg.getBuckets().stream()
                .map(item -> {
                    SearchTmVo tmVo = new SearchTmVo();
                    //设置id
                    tmVo.setTmId(item.getKeyAsNumber().longValue());
                    //设置品牌名
                    tmVo.setTmName(((ParsedStringTerms) item.getAggregations().get("tmNameAgg")).getBuckets().get(0).getKeyAsString());
                    //设置品牌logo
                    tmVo.setTmLogoUrl(((ParsedStringTerms) item.getAggregations().get("tmLogoAgg")).getBuckets().get(0).getKeyAsString());
                    return tmVo;
                }).collect(Collectors.toList());

//        List<SearchTmVo> trademarkList =new ArrayList<>();
//        for (Terms.Bucket item : tmIdAgg.getBuckets()) {
//            SearchTmVo tmVo = new SearchTmVo();
//            //品牌id
//            long tmId = item.getKeyAsNumber().longValue();
//            tmVo.setTmId(tmId);
//            //品牌name   Parsed+字段类型+聚合类型
//            ParsedStringTerms tmNameAgg = item.getAggregations().get("tmNameAgg");
//            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
//            tmVo.setTmName(tmName);
//            //品牌logo
//            ParsedStringTerms tmLogoAgg = item.getAggregations().get("tmLogoAgg");
//            String tmLogo = tmLogoAgg.getBuckets().get(0).getKeyAsString();
//            tmVo.setTmLogoUrl(tmLogo);
//            // ParsedLongTerms
//            // ParsedStringTerms
//            trademarkList.add(tmVo);
//
//        }
        response.setTrademarkList(tmVos);

        //5、属性列表（分析检索到的所有商品涉及了多少种属性，每种属性涉及了多少种值）
        //把所有检索到的商品的属性都拿出来，所有属性进行并集运算、每一种属性有多少值都要分析到。


        //List<SearchRespAttr> attrsList
        ParsedLongTerms attrIdAgg = ((ParsedNested) search.getAggregations().get("attrAgg"))
                .getAggregations().get("attrIdAgg");

        List<SearchRespAttr> attrs = attrIdAgg.getBuckets().stream()
                .map(bucket -> {
                    SearchRespAttr attr = new SearchRespAttr();
                    //属性id
                    attr.setAttrId(bucket.getKeyAsNumber().longValue());

                    //属性名
                    String attrName = ((ParsedStringTerms) bucket.getAggregations().get("attrNameAgg")).getBuckets().get(0).getKeyAsString();
                    attr.setAttrName(attrName);

                    //属性值 可能性
                    ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");
                    List<String> valueList = attrValueAgg.getBuckets().stream()
                            .map(Terms.Bucket::getKeyAsString)
                            .collect(Collectors.toList());
                    attr.setAttrValueList(valueList);
                    return attr;
                }).collect(Collectors.toList());

        //属性列表信息交给前端
        response.setAttrsList(attrs);


        //6、排序信息
        String order = paramVo.getOrder();
        SearchOrderMapVo desc = new SearchOrderMapVo("1", "desc");
        if (!StringUtils.isEmpty(order) && order.contains(":")) {
            //order=1:asc
            String[] split = order.split(":");
            desc.setType(split[0]);
            desc.setSort(split[1]);
        }
        response.setOrderMap(desc); //默认排序


        //7、商品列表
        List<Goods> goodsList = search.getSearchHits()
                .stream()
                .map(item->{
                    Goods content = item.getContent();
                    if(!StringUtils.isEmpty(paramVo.getKeyword())){
                        //提取高亮标题
                        List<String> title = item.getHighlightField("title");
                        content.setTitle(title.get(0));
                    }
                    return content;
                })
                .collect(Collectors.toList());
        response.setGoodsList(goodsList);


        //8、当前页码
        response.setPageNo(paramVo.getPageNo());
        //9、总页码   size:5   总：10
        // 总页码计算公式：总记录数 % pageSize == 0 ? 总记录数/pageSize ： 总记录数/pageSize + 1
        long totalHits = search.getTotalHits();
        long totalPages = totalHits % paramVo.getPageSize() == 0 ? totalHits / paramVo.getPageSize() : (totalHits / paramVo.getPageSize() + 1);
        response.setTotalPages(totalPages);


        //10、urlParam: 当前前端发请求的时候整个完整的url路径。
        //还原前端发来的请求的完整路径：list.html?category3Id=61&pageNo=1&trademark=2:华为
        String url = makeUrlParam(paramVo);
        response.setUrlParam(url);


        return response;
    }

    /**
     * 根据当时前端发送的请求参数，还原出当时请求的完整url地址
     *
     * @param paramVo
     * @return
     */
    private String makeUrlParam(SearchParamVo paramVo) {
        StringBuilder url = new StringBuilder("list.html?");
        //带什么追加什么  url?k=v&k=v&k=v
        if (paramVo.getCategory1Id() != null) {
            url.append("&category1Id=" + paramVo.getCategory1Id());
        }

        if (paramVo.getCategory2Id() != null) {
            url.append("&category2Id=" + paramVo.getCategory2Id());
        }

        if (paramVo.getCategory3Id() != null) {
            url.append("&category3Id=" + paramVo.getCategory3Id());
        }

        // list.html?&category3Id=61&keyword=小米
        if (!StringUtils.isEmpty(paramVo.getKeyword())) {
            url.append("&keyword=" + paramVo.getKeyword());
        }

        if (!StringUtils.isEmpty(paramVo.getTrademark())) {
            url.append("&trademark=" + paramVo.getTrademark());
        }

        //属性
        if (paramVo.getProps() != null && paramVo.getProps().length > 0) {
            for (String prop : paramVo.getProps()) {
                url.append("&props=" + prop);
            }
        }

        //排序不用管
//        if (!StringUtils.isEmpty(paramVo.getOrder())) {
//            url.append("&order=" + paramVo.getOrder());
//        }

        //页码不用管
        // url.append("&pageNo=" + paramVo.getPageNo());


        return url.toString();
    }


}
